/*
 * Created by SharpDevelop.
 * User: EdwardW
 * Date: 30/06/2007
 * Time: 11:04
 */

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using WatiN.Core;
using Wax.Properties;

namespace Wax
{
	/// <summary>
	/// Executes a command against an <see cref="Element" />.
	/// </summary>
	[Serializable]
    public class Command
	{
		#region Private fields
		
		/// <summary>
		/// Id of the command
		/// </summary>
		private string id;
		
		/// <summary>
		/// MethodType of command
		/// </summary>
		private CommandType commandType;
		
		/// <summary>
		/// Comma separated list of <see cref="Command.Arguments">arguments</see> used when executing a <see cref="Command" />
		/// </summary>
		private Collection<string> arguments;
			
		/// <summary>
		/// The <see cref="FindMethod" /> method used to find the <see cref="Element" /> against
		/// which the command is executed.
		/// </summary>
		private FindMethod findMethod;
		
		#endregion
		
		#region Constructors
		
		/// <summary>
		/// Creates a new instance of <see cref="Command" />
		/// </summary>
		public Command()
		{
			this.findMethod = new FindMethod();
			this.id = string.Empty;
			this.arguments = new Collection<string>();
		}
		
		#endregion
		
		#region Public properties
		
		/// <summary>
		/// Id of the command
		/// </summary>
		public string Id
		{
			get { return this.id; }
			set { this.id = value; }
		}
		
		/// <summary>
		/// MethodType of command
		/// </summary>
		public CommandType CommandType
		{
			get { return this.commandType; }
			set { this.commandType = value; }
		}
		
		
		/// <summary>
		/// Comma separated list of <see cref="Command.Arguments">arguments</see> used when executing a <see cref="Command" />
		/// </summary>
		public Collection<string> Arguments
		{
			get { return this.arguments; }
		}
		
		/// <summary>
		/// The <see cref="FindMethod" /> method used to find the <see cref="Element" /> against
		/// which the command is executed.
		/// </summary>
		public FindMethod FindMethod
		{
			get { return this.findMethod; }
			set { this.findMethod = value; }
		}
		
		#endregion
		
		#region Public static methods
		
		#endregion
		
		#region Public instance methods

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
	    public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("Id: " + this.Id + Environment.NewLine);
            stringBuilder.Append("Command type:" + this.CommandType + Environment.NewLine);

            if (this.Arguments.Count > 0)
            {
                stringBuilder.Append("Arguments: ");

                foreach (string argument in arguments)
                {
                    stringBuilder.Append(argument + Environment.NewLine + " ");
                }
            }

            return stringBuilder.ToString();
        }

	    /// <summary>
		/// Executes this command
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
		[CLSCompliant(false)]
        public virtual void Execute(IExecutionContext context)
		{
			if (context == null)
			{
				throw new ArgumentNullException("context");
			}
			
			switch (this.CommandType)
			{
				case CommandType.Back:
					ExecuteBack(context);
					break;
                case CommandType.BringToFront:
			        ExecuteBringToFront(context);
			        break;
				case CommandType.Forward:
					ExecuteForward(context);
					break;
                case CommandType.Focus:
                    ExecuteFocus(context);
                    break;
                case CommandType.Click:
					ExecuteClick(context);
					break;
                case CommandType.ClickNoWait:
                    ExecuteClickNoWait(context);
                    break;
                case CommandType.DoubleClick:
					ExecuteDoubleClick(context);
					break;
				case CommandType.GoTo:
					ExecuteGoto(context);
					break;
                case CommandType.Close:
                    ExecuteClose(context);
                    break;
                case CommandType.AttachToIE:
			        ExecuteAttachToIE(context);
			        break;
                case CommandType.Flash:
                    ExecuteFlash(context);
                    break;
                case CommandType.Refresh:
                    ExecuteRefresh(context);
                    break;
                case CommandType.PressTab:
                    ExecutePressTab(context);
                    break;
                case CommandType.ClearCache:
                    ExecuteClearCache(context);
                    break;
                case CommandType.ClearCookies:
                    ExecuteClearCookies(context);
                    break;
                case CommandType.SetCookie:
                    ExecuteSetCookie(context);
                    break;
                case CommandType.Screenshot:
                    ExecuteScreenshot(context);
                    break;
                case CommandType.GetCookie:
                    ExecuteGetCookie(context);
                    break;
                default:
					throw new ArgumentOutOfRangeException("context", string.Format(CultureInfo.InvariantCulture, "Do not know how to execute the command: {0}", this.CommandType));
			}
		}	    

	    #endregion

	    #region Internal instance methods

	    /// <summary>
	    /// Initializes this instance.
	    /// </summary>
	    internal virtual void Initialize()
	    {
            
	    }

	    #endregion
		
		#region Private instance methods

        /// <summary>
        /// Executes the get cookie command.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteGetCookie(IExecutionContext context)
        {
            context.LastFoundValue = context.Browser.GetCookie(this.Arguments[0], this.Arguments[1]);
        }

        /// <summary>
        /// Executes the click no wait command.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteClickNoWait(IExecutionContext context)
        {
            WatiN.Core.Element element = this.FindMethod.Execute(context);		
			element.ClickNoWait();
        }

        /// <summary>
        /// Executes the set cookie command.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteSetCookie(IExecutionContext context)
        {
            context.Browser.SetCookie(this.Arguments[0], this.Arguments[1]);
        }

	    /// <summary>
        /// Executes the screen shot command.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteScreenshot(IExecutionContext context)
        {
            string fileName = Arguments.Count > 0 ? Arguments[0] : null;

            if (string.IsNullOrEmpty(fileName))
            {
                throw new ExecutionException(Resource.ScreenshotNoFileNameSpecified);
            }

            if (fileName.Contains("~"))
            {
                fileName = fileName.Replace("~", System.Environment.CurrentDirectory);    
            }

            context.Browser.CaptureWebPageToFile(fileName);
        }

        /// <summary>
        /// Executes the clear cookies command.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteClearCookies(IExecutionContext context)
        {
            if (this.Arguments.Count > 0 && !string.IsNullOrEmpty(this.Arguments[0]))
            {
                context.Browser.ClearCookies(this.Arguments[0]);
            }
            else
            {
                context.Browser.ClearCookies();
            }
        }

        /// <summary>
        /// Executes the clear cache command.
        /// </summary>
        /// <param name="context">The context.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteClearCache(IExecutionContext context)
        {
            context.Browser.ClearCache();
        }

        /// <summary>
        /// Executes the press tab command.
        /// </summary>
        /// <param name="context">The context.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecutePressTab(IExecutionContext context)
        {
            context.Browser.PressTab();
        }

        /// <summary>
        /// Executes the refresh command.
        /// </summary>
        /// <param name="context">The context.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteRefresh(IExecutionContext context)
        {
            context.Browser.Refresh();
        }

        /// <summary>
        /// Executes the close command.
        /// </summary>
        /// <param name="context">The context.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteClose(IExecutionContext context)
        {
            context.Browser.Close();
        }

        /// <summary>
        /// Executes the AttachToIE command
        /// </summary>
        /// <param name="context">The execution context used to execute this command</param>
        private void ExecuteAttachToIE(IExecutionContext context)
        {
            switch(this.FindMethod.MethodType)
            {
                case FindMethodTypes.ByTitle:
                    if(!IE.Exists(Find.ByTitle(this.FindMethod.Value)))
                    {
                        StringBuilder errorMessage = new StringBuilder();
                        errorMessage.Append(string.Format(CultureInfo.InvariantCulture,
                                                          "Unable to locate a running instance of Internet Explorer that has the title: {0}",
                                                          this.FindMethod.Value));
                        
                        throw new FindMethodException(errorMessage.ToString());
                    }
                    
                    context.Browser = IE.AttachToIE(Find.ByTitle(this.FindMethod.Value));
                    break;
                default:
                    throw new ArgumentOutOfRangeException("FindMethod.MethodType",
                                                          string.Format(CultureInfo.InvariantCulture,
                                                                        "Do not support attaching to Internet Explorer using the {0} method.",
                                                                        this.FindMethod.MethodType));
            }
        }

		/// <summary>
		/// Executes the Back command, used to navigates to the previous page in the browser's history
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteBack(IExecutionContext context)
		{
			context.Browser.Back();
		}

        /// <summary>
        /// Executes the BringToFront command, causing the current browser window to the top of the windows order
        /// </summary>
        /// <param name="context">The execution context used to execute this command</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteBringToFront(IExecutionContext context)
        {
            context.Browser.BringToFront();
        }


        /// <summary>
        /// Executes the flash command, causing the element located by <see cref="FindMethod"/> to flash.
        /// </summary>
        /// <param name="context">The context.</param>
        private void ExecuteFlash(IExecutionContext context)
        {
            WatiN.Core.Element element = this.FindMethod.Execute(context);
            element.Click();
        }

		/// <summary>
		/// Executes the Forward command, used to navigates forward in the browser's history
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Not performance sensitive code, keeping instance for consistant object design.")]
        private void ExecuteForward(IExecutionContext context)
		{
			context.Browser.Forward();
		}
		
		/// <summary>
		/// Executes the Click command
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
		private void ExecuteClick(IExecutionContext context)
		{
			WatiN.Core.Element element = this.FindMethod.Execute(context);		
			element.Click();
		}

        /// <summary>
        /// Executes the Focus command
        /// </summary>
        /// <param name="context">The execution context used to execute this command</param>
        private void ExecuteFocus(IExecutionContext context)
        {
            WatiN.Core.Element element = this.FindMethod.Execute(context);
            element.Focus();
        }

		/// <summary>
		/// Executes the DoubleClick command
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
		private void ExecuteDoubleClick(IExecutionContext context)
		{
			WatiN.Core.Element element = this.FindMethod.Execute(context);		
			element.DoubleClick();
		}
		
		/// <summary>
		/// Executes the Goto command
		/// </summary>
		/// <param name="context">The execution context used to execute this command</param>
		private void ExecuteGoto(IExecutionContext context)
		{
            try
            {
                string url = this.Arguments[0];
                if (url.StartsWith("~", StringComparison.OrdinalIgnoreCase))
                {
                    url = url.Replace("~", Environment.CurrentDirectory + @"\");
                }

                context.Browser.GoTo(url);    
            }
			catch(Exception unexpectedException)
			{
                throw new ExecutionException(string.Format(CultureInfo.InvariantCulture, Resource.CommandExecutionExceptionMessage, this.ToString()), unexpectedException, this); 
			}
		}
		
		#endregion
	}
}
